<?php defined('INDEX_FILE') or die('error001');


/**
* MLT Web Engine
* http://www.molot-soft.ru
*
* @package MLT_WE
* @subpackage Simple_TPL
* @author Ivan Chistyakov <ivan@molot-soft.ru>
* @version $Id: simple_tpl.inc.php 39 2008-03-18 11:06:38Z molotsoft@gmail.com $
* @link $HeadURL: http://simple-tpl.googlecode.com/svn/trunk/simple_tpl.inc.php $
*/


define('STPL_ROOT_DIR', pathinfo(__FILE__, PATHINFO_DIRNAME) . '/');
define('STPL_PHP_EXT', '.' . pathinfo(__FILE__, PATHINFO_EXTENSION));

define('STPL_PLUGINS_DIR', STPL_ROOT_DIR . 'tags/');
define('STPL_PLUGIN_EXT', '.stpl' . STPL_PHP_EXT);


define('STPL_DEF_TAG', 'tag');
define('STPL_DEF_NAME', 'name');
define('STPL_DEF_LEVEL', 0);

define('STPL_ATTRIBUTE_REGEX', '~\s+([_a-z][a-z0-9_]*)\=(?:\"([^\r\n\"]*)\"|\'([^\r\n\']*)\'|\`([^\r\n\`]*)\`|([^\r\n\s\/\}]+))~i');
define('STPL_TAG_REGEX', '~\{((?:([_a-z][a-z0-9_]*)\:)?([_a-z][a-z0-9_]*)(?:\-([0-9]+))?)((?:\s+[_a-z][a-z0-9_]*\=(?:\"[^\r\n\"]*\"|\'[^\r\n\']*\'|\`[^\r\n\`]*\`|[^\r\n\s\/\}]+))*)(?:\s*\}(.*)\{\/\1\}|\s*\/\})~is');


class simple_tpl_element implements iterator {

	private $tag = STPL_DEF_TAG;
	private $name = STPL_DEF_NAME;
	private $level = STPL_DEF_LEVEL;

	private $value = NULL;
	private $content = '';
	private $attributes = array();

	private $variables = array();
	private $strings = array();

	private $parent = NULL;
	private $children = array();

	final public function __construct($tag = STPL_DEF_TAG, $name = STPL_DEF_NAME, $level = STPL_DEF_LEVEL, $value = NULL, $content = '', array $attributes = array(), $parent = NULL) {
		$this->tag = strtolower($tag);
		$this->name = strtolower($name);
		$this->level = (int)$level;
		$this->value = $value;
		$this->content = $content;
		$this->attributes = $attributes;
		$this->variables = array();
		$this->strings = array();
		$this->parent = $parent;
		$this->children = self::get_parsed_children($this->content, $this);
		// redefine name and value
		$this->name = $this->get_attribute('name', strtolower($name));
		$this->value = $this->get_attribute('value', $value);
	}

	final public function get_class() {
		return get_class($this);
	}

	final public function get_tag() {
		return $this->tag;
	}

	final public function get_name() {
		return $this->name;
	}

	final public function get_top_name() {
		$name = $this->get_name();
		if ($this->has_parent()) {
			$name = $this->get_parent()->get_top_name() . '.' . $name;
		}
		return $name;
	}

	final public function get_level() {
		return $this->level;
	}

	final public function get_value() {
		return $this->value;
	}

	final public function get_content() {
		return $this->content;
	}

	public function get_data() {
		$content = $this->get_content();
		$content = str_ireplace('{!tag}', hsc($this->get_tag()), $content);
		$content = str_ireplace('{!name}', hsc($this->get_name()), $content);
		$content = str_ireplace('{!top_name}', hsc($this->get_top_name()), $content);
		$content = str_ireplace('{!level}', hsc($this->get_level()), $content);
		$content = str_ireplace('{!value}', hsc($this->get_value()), $content);
		foreach ($this->get_attributes() as $name => $value) {
			$content = str_ireplace('{@' . $name . '}', hsc($value), $content);
		}
		foreach ($this->get_variables() as $name => $value) {
			$content = str_ireplace('{$' . $name . '}', hsc($value), $content);
		}
		foreach ($this->get_strings() as $name => $value) {
			$content = str_ireplace('{%' . $name . '}', hsc($value), $content);
		}
		foreach ($this->get_children() as $name => $child) {
			$content = str_ireplace('{?' . $name . '}', $child->get_data(), $content);
		}
		return $content;
	}

	final public function get_attributes() {
		return $this->attributes;
	}

	final public function get_num_attributes() {
		return count($this->get_attributes());
	}

	final public function has_attributes() {
		return $this->get_num_attributes() >= 1;
	}

	final public function isset_attribute($name) {
		return isset($this->attributes[strtolower($name)]);
	}

	final public function get_attribute($name, $default = FALSE) {
		return $this->isset_attribute($name) ? $this->attributes[strtolower($name)] : $default;
	}

	final public function get_variables() {
		return $this->variables;
	}

	final public function get_num_variables() {
		return count($this->get_variables());
	}

	final public function has_variables() {
		return $this->get_num_variables() >= 1;
	}

	final public function isset_variable($name) {
		return isset($this->variables[strtolower($name)]);
	}

	final public function get_variable($name, $default = FALSE) {
		return $this->isset_variable($name) ? $this->variables[strtolower($name)] : $default;
	}

	final public function get_strings() {
		return $this->strings;
	}

	final public function get_num_strings() {
		return count($this->get_strings());
	}

	final public function has_strings() {
		return $this->get_num_strings() >= 1;
	}

	final public function isset_string($name) {
		return isset($this->strings[strtolower($name)]);
	}

	final public function get_string($name, $default = FALSE) {
		return $this->isset_string($name) ? $this->strings[strtolower($name)] : $default;
	}

	final public function get_parent() {
		return $this->parent;
	}

	final public function has_parent() {
		return is_object($this->get_parent()) && is_a($this->get_parent(), __CLASS__);
	}

	final public function get_top_parent() {
		$parent = $this->get_parent();
		while ($this->has_parent() && $parent->has_parent()) {
			$parent = $parent->get_parent();
		}
		return $parent;
	}

	final public function get_children() {
		return $this->children;
	}

	final public function get_num_children() {
		return count($this->get_children());
	}

	final public function has_children() {
		return $this->get_num_children() >= 1;
	}

	final public function isset_child($name) {
		return isset($this->children[strtolower($name)]);
	}

	final public function get_child($name, $default = FALSE) {
		return $this->isset_child($name) ? $this->children[strtolower($name)] : $default;
	}

	final public function rewind() {
		return reset($this->children);
	}

	final public function key() {
		return key($this->children);
	}

	final public function current() {
		return current($this->children);
	}

	final public function next() {
		return next($this->children);
	}

	final public function valid() {
		return $this->current() !== FALSE;
	}

	final public function isset_param($name) {
		return $this->isset_attribute($name) || $this->isset_child($name);
	}

	final public function get_param($name, $default = FALSE) {
		if ($this->isset_attribute($name)) {
			return $this->get_attribute($name);
		} elseif ($this->isset_child($name)) {
			return $this->get_child($name)->get_data();
		} else {
			return $default;
		}
	}

	final public function set_value($value) {
		$this->value = $value;
	}

	final public function set_content($content) {
		$this->content = $content;
	}

	final public function clear_attributes($name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->attributes = array();
		}
		foreach ($this->get_children() as $child) {
			$child->clear_attributes($name_mask, $tag_mask);
		}
	}

	final public function set_attributes(array $attributes = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->attributes = $attributes;
		}
		foreach ($this->get_children() as $child) {
			$child->set_attributes($attributes, $name_mask, $tag_mask);
		}
	}

	final public function merge_attributes(array $attributes = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->attributes = array_merge($this->get_attributes(), $attributes);
		}
		foreach ($this->get_children() as $child) {
			$child->merge_attributes($attributes, $name_mask, $tag_mask);
		}
	}

	final public function clear_variables($name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->variables = array();
		}
		foreach ($this->get_children() as $child) {
			$child->clear_variables($name_mask, $tag_mask);
		}
	}

	final public function set_variables(array $variables = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->variables = $variables;
		}
		foreach ($this->get_children() as $child) {
			$child->set_variables($variables, $name_mask, $tag_mask);
		}
		foreach ($variables as $name => $value) {
			if ($this->get_name() === $name) {
				$this->set_value($value);
			}
		}
	}

	final public function merge_variables(array $variables = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->variables = array_merge($this->get_variables(), $variables);
		}
		foreach ($this->get_children() as $child) {
			$child->merge_variables($variables, $name_mask, $tag_mask);
		}
		foreach ($variables as $name => $value) {
			if ($this->get_name() === $name) {
				$this->set_value($value);
			}
		}
	}

	final public function clear_strings($name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->strings = array();
		}
		foreach ($this->get_children() as $child) {
			$child->clear_strings($name_mask, $tag_mask);
		}
	}

	final public function set_strings(array $strings = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->strings = $strings;
		}
		foreach ($this->get_children() as $child) {
			$child->set_strings($strings, $name_mask, $tag_mask);
		}
	}

	final public function merge_strings(array $strings = array(), $name_mask = '*', $tag_mask = '*') {
		if (mask_match($name_mask, $this->get_name()) && mask_match($tag_mask, $this->get_tag())) {
			$this->strings = array_merge($this->get_strings(), $strings);
		}
		foreach ($this->get_children() as $child) {
			$child->merge_strings($strings, $name_mask, $tag_mask);
		}
	}

	final public static function const_replace($string) {
		return preg_replace('~%([_a-z][a-z0-9_]*)%~ie', '(defined(strtoupper(\'stpl_\'.\'$1\'))?constant(strtoupper(\'stpl_\'.\'$1\')):\'\');', $string);
	}

	final public static function get_parsed_attributes($attributes_string) {
		$attributes = array();
		// attributes parsing
		$shift = 0;
		while (preg_match(STPL_ATTRIBUTE_REGEX, $attributes_string, $matches, PREG_OFFSET_CAPTURE, $shift) === 1) {
			// offset / len
			$offset = $matches[0][1];
			$len = strlen($matches[0][0]);
			// parameters
			$name = strtolower($matches[1][0]);
			$value = $matches[count($matches) - 1][0];
			// add attribute
			if (!isset($attributes[$name])) {
				$attributes[$name] = self::const_replace($value);
			}
			// next attribute shift
			$shift = $offset + $len;
		}
		// end of attributes parsing
		return $attributes;
	}

	final public static function get_parsed_children(&$children_string, $parent = NULL) {
		$children = array();
		// children parsing
		$shift = 0;
		while (preg_match(STPL_TAG_REGEX, $children_string, $matches, PREG_OFFSET_CAPTURE, $shift) === 1) {
			// offset / len
			$offset = $matches[0][1];
			$len = strlen($matches[0][0]);
			// parameters
			$tag = !empty($matches[2][0]) ? strtolower($matches[2][0]) : STPL_DEF_TAG;
			$name = strtolower($matches[3][0]);
			$level = !empty($matches[4][0]) ? (int)$matches[4][0] : STPL_DEF_LEVEL;
			$attributes = self::get_parsed_attributes($matches[5][0]);
			$content = isset($matches[6][0]) ? $matches[6][0] : '';
			// add child
			if (!isset($children[$name])) {
				// class_name
				$class_name = sprintf('stpl_%s_element', $tag);
				if (empty($tag) || ($tag === STPL_DEF_TAG) || !subclass_exists($class_name, __CLASS__, FALSE)) {
					$tag = STPL_DEF_TAG;
					$class_name = __CLASS__;
				}
				// new tag
				$children[$name] = new $class_name($tag, $name, $level, NULL, $content, $attributes, $parent);
				// replace tag
				$tag_link = '{?' . $name . '}';
				$children_string = substr_replace($children_string, $tag_link, $offset, $len);
				$len = strlen($tag_link);
			}
			// next child shift
			$shift = $offset + $len;
		}
		// end of children parsing
		return $children;
	}

	final public function __set($name, $value) {
		// disabled
	}

	final public function __get($name) {
		return $this->get_child($name);
	}

	final public function __isset($name) {
		return $this->isset_child($name);
	}

	final public function __unset($name) {
		// disabled
	}

	final public function __destruct() {
		// empty
	}

}


foreach (glob_r(STPL_PLUGINS_DIR . '*' . STPL_PLUGIN_EXT) as $filename) {
	require_once($filename);
}


function simple_tpl_load_string($data = '') {
	return new simple_tpl_element(STPL_DEF_TAG, STPL_DEF_NAME, STPL_DEF_LEVEL, NULL, $data);
}

function simple_tpl_load_file($filename) {
	return simple_tpl_load_string(file_get_contents($filename));
}


?>